package cn.seaboot.admin.mvc.utils;

import cn.seaboot.commons.core.MapUtils;
import freemarker.cache.StringTemplateLoader;
import freemarker.template.Configuration;
import freemarker.template.TemplateException;
import freemarker.template.TemplateNotFoundException;

import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.util.List;
import java.util.Map;

/**
 * 带缓存的 Freemarker 渲染工具
 * <p>
 * 首先这不是一个静态类，而且不是直接能使用的类，
 * 使用过程中，需要手动调整模版缓存，
 * 就像一个 map，能用来制作缓存，但是需要手动地 put 缓存。
 *
 * @author Mr.css
 * @version 2023-04-27 14:40
 */
public class FreemarkerRender {
    /**
     * 配置信息
     */
    private Configuration configuration;
    /**
     * 缓存加载器
     */
    private StringTemplateLoader stringTemplateLoader = new StringTemplateLoader();

    /**
     * 使用与 Freemarker 完全一致的默认配置
     */
    public FreemarkerRender() {
        this((Configuration) Freemarker.getConfiguration().clone());
    }

    /**
     * 手动指定配置
     */
    public FreemarkerRender(Configuration config) {
        this.setConfiguration(config);
    }

    /**
     * 获取配置信息
     *
     * @return -
     */
    public Configuration getConfiguration() {
        return configuration;
    }

    /**
     * 复制配置信息，默认拷贝{@link Freemarker}的配置，
     * 但是需要注意，执行{@link Freemarker#setConfiguration(Configuration)}，不会触发当前函数
     *
     * @param config 配置信息
     */
    public void setConfiguration(Configuration config) {
        this.configuration = config;
        this.configuration.setTemplateLoader(stringTemplateLoader);
    }

    /**
     * 会直接覆盖原有的模版，如果想避免覆盖，需要调用{@link FreemarkerRender#contain(String)}判断是否已经存在该模版。
     *
     * @param cacheName 缓存名称
     * @param tpl       字符串模版
     */
    public void putTemplate(String cacheName, String tpl) {
        stringTemplateLoader.putTemplate(cacheName, tpl);
    }

    /**
     * 是否已经存在模版缓存
     *
     * @param cacheName 缓存名称
     * @return true/false
     */
    public boolean contain(String cacheName) {
        return stringTemplateLoader.findTemplateSource(cacheName) != null;
    }

    /**
     * 移除缓存
     *
     * @param cacheName 缓存名称
     * @return StringTemplateSource/null {@link StringTemplateLoader}
     */
    public boolean removeTemplate(String cacheName) {
        return stringTemplateLoader.removeTemplate(cacheName);
    }

    /**
     * 重置TemplateLoader，清除所有缓存
     */
    public void resetTemplateLoader() {
        this.stringTemplateLoader = new StringTemplateLoader();
        this.configuration.setTemplateLoader(stringTemplateLoader);
    }

    /**
     * 渲染模版
     *
     * @param cacheName 缓存名称
     * @param model     模型
     * @param writer    输出对象
     * @throws IOException               -
     * @throws TemplateNotFoundException 缓存不存在
     * @throws TemplateException         -
     */
    public void render(String cacheName, Object model, Writer writer) throws IOException, TemplateException {
        this.configuration.getTemplate(cacheName).process(model, writer);
    }

    /**
     * 渲染模版
     *
     * @param cacheName 缓存名称
     * @param model     模型
     * @param writer    输出对象
     * @throws IOException               -
     * @throws TemplateNotFoundException 缓存不存在
     * @throws TemplateException         -
     */
    public void render(String cacheName, List<?> model, Writer writer) throws IOException, TemplateException {
        this.render(cacheName, getArrayModel(model), writer);
    }

    /**
     * 渲染模版
     *
     * @param cacheName 缓存名称
     * @param model     模型
     * @param writer    输出对象
     * @throws IOException               -
     * @throws TemplateNotFoundException 缓存不存在
     * @throws TemplateException         -
     */
    public void render(String cacheName, Object[] model, Writer writer) throws IOException, TemplateException {
        this.render(cacheName, getArrayModel(model), writer);
    }

    /**
     * 渲染模版
     *
     * @param cacheName 缓存名称
     * @param model     模型
     * @throws IOException               -
     * @throws TemplateNotFoundException 缓存不存在
     * @throws TemplateException         -
     */
    public String render(String cacheName, Object model) throws IOException, TemplateException {
        StringWriter out = new StringWriter();
        render(cacheName, model, out);
        return out.toString();
    }

    /**
     * 渲染模版
     *
     * @param cacheName 缓存名称
     * @param model     模型
     * @throws IOException               -
     * @throws TemplateNotFoundException 缓存不存在
     * @throws TemplateException         -
     */
    public String render(String cacheName, List<Object> model) throws IOException, TemplateException {
        return render(cacheName, getArrayModel(model));
    }

    /**
     * 渲染模版
     *
     * @param cacheName 缓存名称
     * @param model     模型
     * @throws IOException               -
     * @throws TemplateNotFoundException 缓存不存在
     * @throws TemplateException         -
     */
    public String render(String cacheName, Object[] model) throws IOException, TemplateException {
        return render(cacheName, getArrayModel(model));
    }

    /**
     * 对象模型只允许是Map或者JavaBean，为了方便操作，做一个简单的封装，将list或者数组转成Map
     *
     * @param model 数据模型
     * @return map
     */
    private static Map<String, Object> getArrayModel(Object model) {
        return MapUtils.newMap("list", model);
    }
}